package com.ybliang.plat.common.module.redis.configuration;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.ybliang.plat.common.module.redis.configuration.properties.MultiRedisProperties;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.ObjectUtils;

/**
 * @author liangyb1
 */
public class RedisImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    public static Logger LOGGER = LoggerFactory.getLogger(RedisImportBeanDefinitionRegistrar.class);

    private static final String MASTER_REDIS_BEAN_NAME = "master";

    private Environment environment;

    private MultiRedisProperties multiRedisProperties;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
        multiRedisProperties = Binder.get(environment)
                .bind(MultiRedisProperties.PREFIX, MultiRedisProperties.class).get();
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if(null == multiRedisProperties || multiRedisProperties.getMultis().isEmpty()) {
            return ;
        }
        multiRedisProperties.getMultis().keySet().forEach(beanNamePrefix -> registerBean(beanNamePrefix, registry));
    }

    private void registerBean(String beanNamePrefix, BeanDefinitionRegistry registry) {
        String suffix = "RedisTemplate";
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RedisTemplate.class, () -> createRedisTemplate(beanNamePrefix));
        String beanName = beanNamePrefix + suffix;
        String aliasBeanName = "";
        registry.registerBeanDefinition(beanName, rootBeanDefinition);
        if(MASTER_REDIS_BEAN_NAME.equals(beanNamePrefix)) {
            aliasBeanName = suffix.substring(0, 1).toLowerCase() + suffix.substring(1);
            // beanNamePrefix 为master时(即beanName = masterRedisTemplate)，bean注册别名:redisTemplate
            registry.registerAlias(beanName, aliasBeanName);
        }
        LOGGER.info("RedisImportBeanDefinitionRegistrar register bean:{}, alias:{}, done", beanName, aliasBeanName);
    }

    private RedisTemplate<String, Object> createRedisTemplate(String beanNamePrefix) {
        RedisProperties redisProperties = multiRedisProperties.getMultis().get(beanNamePrefix);
        RedisConnectionFactory redisConnectionFactory = buildRedisConnectionFactory(redisProperties);
        RedisTemplate<String, Object>    redisTemplate    = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(valueSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(valueSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }


    private RedisConnectionFactory buildRedisConnectionFactory(RedisProperties properties) {

        RedisProperties.Lettuce lettuce;
        RedisProperties.Pool    pool;

        // ========= 基本配置 =========
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(properties.getHost());
        configuration.setPort(properties.getPort());
        configuration.setDatabase(properties.getDatabase());
        if (!ObjectUtils.isEmpty(properties.getPassword())) {
            RedisPassword redisPassword = RedisPassword.of(properties.getPassword());
            configuration.setPassword(redisPassword);
        }

        // ========= 连接池通用配置 =========
        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        if (null != (lettuce = properties.getLettuce())
                && null != (pool = lettuce.getPool())) {
            genericObjectPoolConfig.setMaxTotal(pool.getMaxActive());
            genericObjectPoolConfig.setMaxIdle(pool.getMaxIdle());
            genericObjectPoolConfig.setMinIdle(pool.getMinIdle());
            if (pool.getMaxWait() != null) {
                genericObjectPoolConfig.setMaxWaitMillis(pool.getMaxWait().toMillis());
            }
        }

        // ========= lettuce pool =========
        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
        builder.poolConfig(genericObjectPoolConfig);
        builder.commandTimeout(properties.getTimeout());
        LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(configuration, builder.build());
        connectionFactory.afterPropertiesSet();

        return connectionFactory;
    }


    private RedisSerializer<Object> valueSerializer() {
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        // 类必须是非final修饰的
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(objectMapper);
        return serializer;
    }

}
